﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Web;
using System.Web.Compilation;
using System.IO;
using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Data;

namespace System.Web.Mvc {
    public static class LinkExtensions {

        #region URLs
        internal static string BuildUrl(this HtmlHelper helper, string controllerName, string actionName, string queryString) {
            //TODO: this needs to be built from RouteData
            if (controllerName.EndsWith("Controller"))
                controllerName = controllerName.Replace("Controller", "");

            UrlHelper h = new UrlHelper(helper.ViewContext);
            string link = h.Action(new { controller = controllerName, action = actionName });
            if (!String.IsNullOrEmpty(queryString))
                link += queryString;

            return link;

        }
        internal static string BuildUrlFromExpression<T>(this HtmlHelper helper, Expression<Action<T>> action) where T:Controller {
            StringBuilder sb = new StringBuilder();
            MethodCallExpression call = action.Body as MethodCallExpression;
            if (call == null) throw new InvalidOperationException("Expression must be a method call");
            if (call.Object != action.Parameters[0]) throw new InvalidOperationException("Method call must target lambda argument");

            
            string actionName = call.Method.Name;
            string controllerName = typeof(T).Name;
            string queryString = BuildQueryStringFromExpression(call);


            return BuildUrl(helper, controllerName, actionName, queryString); ;
        }
        internal static string BuildQueryStringFromExpression(MethodCallExpression call) {
            StringBuilder sb = new StringBuilder();
            
            ParameterInfo[] parameters = call.Method.GetParameters();
            

            if (parameters.Length > 0) {

                sb.Append("/");

                for (int i = 0; i < parameters.Length; i++) {


                    //first param (position 0)
                    //is treated as inline URL arg
                    //e.g. http://mysite/home/show/1
                    //TODO: Get with Eilon and figure out how multiple params are handled in the URL
                    if (i == 1) {
                        sb.Append("?");
                        sb.Append(parameters[i].Name);
                        sb.Append("=");
                    } else if(i>1) {
                        sb.Append("&");
                        sb.Append(parameters[i].Name);
                        sb.Append("=");
                    }

                    Expression arg = call.Arguments[i];
                    object value;
                    ConstantExpression ce = arg as ConstantExpression;
                    if (ce != null) {
                        // If argument is a constant expression, just get the value
                        value = ce.Value;
                    } else {
                        // Otherwise, convert the argument subexpression to type object,
                        // make a lambda out of it, compile it, and invoke it to get the value
                        var lambda = Expression.Lambda<Func<object>>(Expression.Convert(arg, typeof(object)));
                        try {
                            value = lambda.Compile()();
                        } catch {
                            // ?????
                            value = "";
                        }
                    }
                    // Code should be added here to appropriately escape the value string
                    sb.Append(value);
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Creates a client-resolvable Url based on the passed-in value
        /// </summary>
        /// <param name="virtualUrl">Relative Url to evaluate. Use ~/ to resolve from the root</param>
        /// <returns></returns>
        public static string ResolveUrl(this HtmlHelper helper, string virtualUrl){

            //TODO: Make this work with IUrlResolutionService
            IHttpContext ctx = helper.ViewContext.HttpContext;
            string result = virtualUrl;
            
            if (virtualUrl.StartsWith("~/")) {
                virtualUrl = virtualUrl.Remove(0, 2);

                //get the site root
                string siteRoot = ctx.Request.ApplicationPath;
                
                if (siteRoot == string.Empty)
                    siteRoot = "/";

                result = siteRoot + virtualUrl;

            }
            return result;
        }

        #endregion


        #region Anchor tags

        /// <summary>
        /// Creates an anchor tag based on the passed in controller type and method
        /// </summary>
        /// <typeparam name="T">The Controller Type</typeparam>
        /// <param name="action">The Method to route to</param>
        /// <param name="linkText">The linked text to appear on the page</param>
        /// <returns>System.String</returns>
        public static string ActionLink<T>(this HtmlHelper helper, Expression<Action<T>> action, string linkText) where T : Controller {

            return ActionLink<T>(helper, action, linkText, null);

        }

        /// <summary>
        /// Creates an anchor tag based on the passed in controller type and method
        /// </summary>
        /// <typeparam name="T">The Controller Type</typeparam>
        /// <param name="action">The Method to route to</param>
        /// <param name="linkText">The linked text to appear on the page</param>
        /// <returns>System.String</returns>
        public static string ActionLink<T>(this HtmlHelper helper, Expression<Action<T>> action, string linkText, object htmlAttributes) where T : Controller {
            
            //TODO: refactor this to work with ActionLink in the core
            string linkFormat = "<a href=\"{0}\" {1}>{2}</a>";
            string atts = string.Empty;

            if (htmlAttributes != null)
                atts = htmlAttributes.ToAttributeList();

            string link = BuildUrlFromExpression(helper, action);
            string result = string.Format(linkFormat, link, atts, helper.Encode(linkText));
            return result;

        }

        #endregion


        #region MailTo


        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <returns></returns>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText) {
            return MailTo(helper, emailAddress, linkText, "", "", "", "", null);
        }

        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <returns></returns>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, string subject) {
            return MailTo(helper, emailAddress, linkText, subject, "", "", "", null);
        }

        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <param name="body">Text you want to appear in the body of the email</param>
        /// <returns></returns>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, string subject, string body) {
            return MailTo(helper, emailAddress, linkText, subject, "", "", body, null);
        }

        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <param name="body">Text you want to appear in the body of the email</param>
        /// <param name="cc">Copy-to email address</param>
        /// <param name="bcc">Blind copy address</param>
        /// <returns></returns>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, string subject, string body, string cc, string bcc) {
            return MailTo(helper, emailAddress, linkText, subject, cc, bcc, body, null);
        }



        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <returns></returns>
        /// <param name="htmlAttributes">Any extra HTML attributes you want to appear. Use Anonymous typing for this (new{_class=myclass})</param>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, object htmlAttributes) {
            return MailTo(helper, emailAddress, linkText, "", "", "", "", htmlAttributes);
        }

        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <returns></returns>
        /// <param name="htmlAttributes">Any extra HTML attributes you want to appear. Use Anonymous typing for this (new{_class=myclass})</param>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, string subject, object htmlAttributes) {
            return MailTo(helper, emailAddress, linkText, subject, "", "", "", htmlAttributes);
        }

        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <param name="body">Text you want to appear in the body of the email</param>
        /// <returns></returns>
        /// <param name="htmlAttributes">Any extra HTML attributes you want to appear. Use Anonymous typing for this (new{_class=myclass})</param>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, string subject, string body, object htmlAttributes) {
            return MailTo(helper, emailAddress, linkText, subject, "", "", body, htmlAttributes);
        }



        /// <summary>
        /// Creates a MailTo link
        /// </summary>
        /// <param name="emailAddress">The address of the recipient. For multiple addresses, use a comma</param>
        /// <param name="linkText">The text of the MailTo link</param>
        /// <param name="subject">Subject of the email</param>
        /// <param name="body">Text you want to appear in the body of the email</param>
        /// <param name="cc">Copy-to email address</param>
        /// <param name="bcc">Blind copy address</param>
        /// <param name="htmlAttributes">Any extra HTML attributes you want to appear. Use Anonymous typing for this (new{_class=myclass})</param>
        /// <returns></returns>
        public static string MailTo(this HtmlHelper helper, string emailAddress, string linkText, string subject, string body, string cc, string bcc, object htmlAttributes) {
            string mailFormat = "<a href=\"{0}\" {1} >{2}</a>";
            string mailToUrl="mailto:"+emailAddress;
            List<string> mailQuery=new List<string>();

            string atts = string.Empty;
            if (htmlAttributes != null)
                atts = htmlAttributes.ToAttributeList();

            if(subject!=string.Empty)
                mailQuery.Add("subject="+helper.Encode(subject));

            if(cc!=string.Empty)
                mailQuery.Add("cc=" + helper.Encode(cc));

            if(bcc!=string.Empty)
                mailQuery.Add("bcc=" + helper.Encode(bcc));

            if(body!=string.Empty)
                mailQuery.Add("body=" + helper.Encode(body));

            int index=0;
            foreach(string s in mailQuery){
                if(index==0)
                    mailToUrl+="?";
                else
                    mailToUrl+="&";

                mailToUrl+=s;
                index++;
            }

            string result = string.Format(mailFormat, mailToUrl, atts, helper.Encode(linkText));
            return result;

        }

        #endregion


    }
}
